package com.gitee.wsl.func.factory.sample

import com.gitee.wsl.func.factory.Factory
import java.io.*
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method


/**
 * Factory implementation that creates a new instance each time based on a prototype.
 *
 *
 * **WARNING:** from v4.1 onwards [Factory] instances returned by
 * [.prototypeFactory] will **not** be serializable anymore in order
 * to prevent potential remote code execution exploits. Please refer to
 * [COLLECTIONS-580](https://issues.apache.org/jira/browse/COLLECTIONS-580)
 * for more details.
 *
 *
 * @since 3.0
 */
object PrototypeFactory {
    /**
     * Factory method that performs validation.
     *
     *
     * Creates a Factory that will return a clone of the same prototype object
     * each time the factory is used. The prototype will be cloned using one of these
     * techniques (in order):
     *
     *
     *
     *  * public clone method
     *  * public copy constructor
     *  * serialization clone
     *
     *
     * @param <T>  the type the factory creates
     * @param prototype  the object to clone each time in the factory
     * @return the `prototype` factory, or a [ConstantFactory.NULL_INSTANCE] if
     * the `prototype` is `null`
     * @throws IllegalArgumentException if the prototype cannot be cloned
    </T> */
    fun <T> prototypeFactory(prototype: T?): Factory<T?> {
        if (prototype == null) {
            return ConstantFactory.constantFactory(null)
        }
        try {
            val method: Method = prototype.javaClass.getMethod("clone", null)
            return PrototypeCloneFactory(prototype, method)
        } catch (ex: NoSuchMethodException) {
            try {
                prototype.javaClass.getConstructor(prototype.javaClass)
                return InstantiateFactory(
                    prototype.javaClass as Class<T>,
                    arrayOf<Class<*>>(prototype.javaClass),
                    arrayOf<Any>(prototype)
                )
            } catch (ex2: NoSuchMethodException) {
                if (prototype is Serializable) {
                    return PrototypeSerializationFactory(prototype as Serializable) as Factory<T?>
                }
            }
        }
        throw IllegalArgumentException("The prototype must be cloneable via a public clone method")
    }

    /**
     * PrototypeCloneFactory creates objects by copying a prototype using the clone method.
     *
     * @param <T> the type of results supplied by this supplier.
    </T> */
    internal class PrototypeCloneFactory<T>(
        /** The object to clone each time  */
        private val iPrototype: T?,
        method: Method?,
    ) : Factory<T?> {
        /** The method used to clone  */
        @Transient
        private var iCloneMethod: Method?

        /**
         * Constructor to store prototype.
         */
        init {
            iCloneMethod = method
        }

        /**
         * Creates an object by calling the clone method.
         *
         * @return the new object
         */
        public override fun create(): T? {
            // needed for post-serialization
            if (iCloneMethod == null) {
                findCloneMethod()
            }

            try {
                return iCloneMethod!!.invoke(iPrototype, null as Array<Any>?) as T?
            } catch (ex: IllegalAccessException) {
                throw FunctorException("PrototypeCloneFactory: Clone method must be public", ex)
            } catch (ex: InvocationTargetException) {
                throw FunctorException("PrototypeCloneFactory: Clone method threw an exception", ex)
            }
        }

        /**
         * Find the Clone method for the class specified.
         */
        private fun findCloneMethod() {
            try {
                iCloneMethod = iPrototype!!.javaClass.getMethod("clone", null)
            } catch (ex: NoSuchMethodException) {
                throw IllegalArgumentException("PrototypeCloneFactory: The clone method must exist and be public ")
            }
        }
    }

    /**
     * PrototypeSerializationFactory creates objects by cloning a prototype using serialization.
     *
     * @param <T> the type of results supplied by this supplier.
    </T> */
    internal class PrototypeSerializationFactory<T : Serializable?>(
        /** The object to clone via serialization each time  */
        private val iPrototype: T?,
    ) : Factory<T?> {
        /**
         * Creates an object using serialization.
         *
         * @return the new object
         */
         override fun create(): T? {
            val baos = ByteArrayOutputStream(512)
            var bais: ByteArrayInputStream? = null
            try {
                val out = ObjectOutputStream(baos)
                out.writeObject(iPrototype)

                bais = ByteArrayInputStream(baos.toByteArray())
                val `in` = ObjectInputStream(bais)
                return `in`.readObject() as T?
            } catch (ex: ClassNotFoundException) {
                throw FunctorException(ex)
            } catch (ex: IOException) {
                throw FunctorException(ex)
            } finally {
                try {
                    bais?.close()
                } catch (ex: IOException) { //NOPMD
                    // ignore
                }
                try {
                    baos.close()
                } catch (ex: IOException) { //NOPMD
                    // ignore
                }
            }
        }
    }
}
